package regular2MR;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;

import org.apache.hadoop.filecache.DistributedCache;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

public class EvalMapper extends Mapper<Text, Text, IntWritable, RvecWritable> {

	public void map(Text key, Text value, Context context) throws IOException,
			InterruptedException {

		// get query
		Path[] fileCaches = DistributedCache.getLocalCacheFiles(context
				.getConfiguration());
		GraphQuery gq = Ultility.getAutomaton(fileCaches[0]);
		//System.out.print(gq.toString());

		// get fragment info
		ArrayList<Vertex> listViNode = new ArrayList<Vertex>();
		ArrayList<Vertex> listInNode = new ArrayList<Vertex>();
		ArrayList<Vertex> listOutNode = new ArrayList<Vertex>();

		LoadDataTung(value, listViNode, listInNode, listOutNode);
		ArrayList<Rvec> rvset = localEval(listViNode, listInNode, listOutNode,
				gq);

		RvecWritable values = new RvecWritable();
		values.rvset = rvset;
		// System.out.println(values.toString());
		IntWritable k = new IntWritable(1);
		context.write(k, values);

	}

	public GraphQuery getAutomaton() {
		// construct the query automaton
		VertexQ vq0 = new VertexQ("4");
		VertexQ vq1 = new VertexQ("DB");
		VertexQ vq2 = new VertexQ("HR");
		VertexQ vq3 = new VertexQ("12");

		vq0.childrenState = new ArrayList<EdgeQ>(Arrays.asList(new EdgeQ(
				vq1.stateName), new EdgeQ(vq2.stateName)));
		vq1.childrenState = new ArrayList<EdgeQ>(Arrays.asList(new EdgeQ(
				vq1.stateName), new EdgeQ(vq3.stateName)));
		vq2.childrenState = new ArrayList<EdgeQ>(Arrays.asList(new EdgeQ(
				vq2.stateName), new EdgeQ(vq3.stateName)));

		ArrayList<VertexQ> Vq = new ArrayList<VertexQ>(Arrays.asList(vq0, vq1,
				vq2, vq3));
		GraphQuery gq = new GraphQuery(Vq, vq0, vq3);
		return gq;
	}

	public GraphQuery getAutomaton1() {
		// construct the query automaton
		VertexQ vq0 = new VertexQ("2124");
		VertexQ vq1 = new VertexQ("Film & Animation");
		VertexQ vq2 = new VertexQ("Entertainment");
		VertexQ vq3 = new VertexQ("949");

		vq0.childrenState = new ArrayList<EdgeQ>(Arrays.asList(new EdgeQ(
				vq1.stateName), new EdgeQ(vq2.stateName)));
		vq1.childrenState = new ArrayList<EdgeQ>(Arrays.asList(new EdgeQ(
				vq1.stateName), new EdgeQ(vq3.stateName)));
		vq2.childrenState = new ArrayList<EdgeQ>(Arrays.asList(new EdgeQ(
				vq2.stateName), new EdgeQ(vq3.stateName)));

		ArrayList<VertexQ> Vq = new ArrayList<VertexQ>(Arrays.asList(vq0, vq1,
				vq2, vq3));
		GraphQuery gq = new GraphQuery(Vq, vq0, vq3);
		return gq;
	}

	public static ArrayList<String> listMarked;

	/**
	 * @param vfi
	 *            : a set of vertices in fragment i
	 * @param inodes
	 *            : a set of input node
	 * @param onodes
	 *            : a set of output node
	 * @param s
	 *            : start node in query
	 * @param t
	 *            : terminal node in query
	 * @param gq
	 *            : query automaton
	 * @return a set of vectors of the boolean formula
	 */
	public static ArrayList<Rvec> localEval(ArrayList<Vertex> vfi,
			ArrayList<Vertex> inodes, ArrayList<Vertex> onodes, GraphQuery gq) {

		ArrayList<Rvec> rvset = new ArrayList<Rvec>();

		ArrayList<VertexQ> Vq = gq.Vq; // get all vertex in query
		VertexQ ut = gq.ut; // get terminal state

		Vertex s = new Vertex(gq.us.stateName); // get start vertex
		Vertex t = new Vertex(gq.ut.stateName); // get terminal vertex

		ArrayList<Vertex> iset = inodes; // set in-nodes
		ArrayList<Vertex> oset = onodes; // set virtual nodes

		if (vfi.contains(t)) {
			if (!oset.contains(t)) {
				int index = vfi.indexOf(t);
				vfi.get(index).label = t.name;
				t = vfi.get(index);
				oset.add(t);
			} else {
				int index1 = oset.indexOf(t);
				oset.get(index1).label = t.name;

				int index2 = vfi.indexOf(t);
				vfi.get(index2).label = t.name;
			}

		}
		
//		for(Vertex vt: iset){
//			if(vt.name.equals("1775"))
//				for(Edge ed: vt.neighbors)
//					System.out.print(ed.target+"\t");
//		}

		if (vfi.contains(s) && !oset.contains(s)) {
			if (!iset.contains(s)) {
				int index = vfi.indexOf(s);
				vfi.get(index).label = s.name;
				s = vfi.get(index);
				iset.add(s);
			} else {
				int index1 = iset.indexOf(s);
				iset.get(index1).label = s.name;

				int index2 = vfi.indexOf(s);
				vfi.get(index2).label = s.name;
			}
		}

		for (Vertex v : vfi)
			v.visit = false;

		for (Vertex v : oset) {
			v.rvec = new ArrayList<Rvec>(); // set empty

			// set value for v where indicate v can match of state u
			for (VertexQ u : Vq) {
				// v is terminal node
				if (v.equals(t) && u.equals(ut))
					v.rvec.add(new Rvec(v.name, u.stateName,
							new ArrayList<BoolFormula>(Arrays
									.asList(new BoolFormula(true)))));
				// v is match of state u
				else if (v.L().equals(u.Lq()))
					v.rvec.add(new Rvec(v.name, u.stateName,
							new ArrayList<BoolFormula>(Arrays
									.asList(new BoolFormula(new X(v.name,
											u.stateName))))));
				// v is not match of state u
				else
					v.rvec.add(new Rvec(v.name, u.stateName,
							new ArrayList<BoolFormula>(Arrays
									.asList(new BoolFormula(false)))));
			}

			v.visit = true;
		}

		// compute vector boolean formula for each input node
		for (Vertex v : iset) {
			listMarked = new ArrayList<String>();
			v.rvec = cmpRvec(v, vfi, gq);
			rvset.addAll(v.rvec);
		}
		return rvset;
	}

	/**
	 * @param v
	 *            : input node
	 * @param vfi
	 *            : list of nodes in V
	 * @param gq
	 *            : query automaton
	 * @return: a vector Rvec of boolean formula
	 */
	public static ArrayList<Rvec> cmpRvec(Vertex v, ArrayList<Vertex> vfi,
			GraphQuery gq) {

		ArrayList<VertexQ> Vq = gq.Vq; // get all vertex in query

		if (v.visit == true)
			return v.rvec;

		listMarked.add(v.name);

		// initial false for all state by v
		for (VertexQ u : Vq)
			v.rvec.add(new Rvec(v.name, u.stateName,
					new ArrayList<BoolFormula>(Arrays.asList(new BoolFormula(
							false)))));

		// get list children node of v
		ArrayList<Vertex> childrenOfV = new ArrayList<Vertex>();
		for (Edge e : v.neighbors)
			if (!listMarked.contains(e.target)) {
				Vertex child = getVertex(e.target, vfi);
				if (child != null)
					childrenOfV.add(child);
			}

		// if v do not child, set visited
		if (childrenOfV.size() == 0)
			v.visit = true;
		else {
			// recurse visit children node and compute vector Rvec via them
			for (Vertex w : childrenOfV) {
				if (w.visit == false)
					w.rvec = cmpRvec(w, vfi, gq);

				int id = 0;
				for (VertexQ u : Vq) {

					if (v.L().equals(u.Lq())) {

						Rvec r = v.rvec.get(id);
						ArrayList<BoolFormula> list = r.value;

						if (list.get(0).value == false) {
							// compose value from children node w
							// where w match of children state of u
							ArrayList<BoolFormula> value = cmposeVec(u, w,
									w.rvec, gq);

							if (!value.isEmpty()) {
								if (list.get(0).boolVar == null)
									list.remove(0);

								for (BoolFormula bf : value)
									if (bf.boolVar != null) {
										if (!list.contains(bf))
											list.add(bf);
									} else if (bf.value == true) {
										list.clear();
										list.add(bf);
									} 
//									else if (bf.boolVar == null
//											&& bf.value == false)
//										list.add(bf);
								if(list.size()==0)
									list.add(new BoolFormula(false));
								
								v.rvec.set(id, new Rvec(v.name, u.stateName,
										list));
							}
							break;
						}

					}
					id++;
				}
				v.visit = true;
			}
		}

		return v.rvec;
	}

	/**
	 * @param u
	 *            : a state in Vq
	 * @param w
	 *            : a vertex in Vi
	 * @param wRvec
	 *            : a vector of Boolean formula of node w
	 * @return list of boolean formula
	 */
	public static ArrayList<BoolFormula> cmposeVec(VertexQ u, Vertex w,
			ArrayList<Rvec> wRvec, GraphQuery gq) {

		ArrayList<VertexQ> childrenOfU = new ArrayList<VertexQ>();
		ArrayList<BoolFormula> list = new ArrayList<BoolFormula>();

		// Get children state of u
		for (EdgeQ e : u.childrenState) {
			VertexQ child = getVertexQ(e.target, gq.Vq);
			childrenOfU.add(child);
		}

		// compose value
		for (VertexQ v : childrenOfU)
			if (w.L().equals(v.Lq())) {
				for (Rvec r : wRvec)
					if (r.u.equals(v.Lq())) {
						list = r.value;
						break;
					}
				break;
			}
		return list;
	}

	/**
	 * @param filePath
	 */
	public static void LoadData(Text value, ArrayList<Vertex> listViNode,
			ArrayList<Vertex> listInNode, ArrayList<Vertex> listOutNode) {
		try {
			byte[] bytes = value.getBytes();
			ByteArrayInputStream arr = new ByteArrayInputStream(bytes);
			BufferedReader bNode = new BufferedReader(
					new InputStreamReader(arr));

			String strLine = null;
			while ((strLine = bNode.readLine()) != null) {
				String[] row = strLine.split("\t");
				String[] node = row[0].split(":"); // split to get node id &
													// determine input node

				// node in fragment
				Vertex vi;
				if (row.length >= 2)
					vi = new Vertex(node[0], row[1]);
				else
					vi = new Vertex(node[0]);

				if (!node[1].equals("2")) {
					// get neighbors
					for (int i = 2; i < row.length; i++) {
						Vertex vj = new Vertex(row[i]);
						if (!listViNode.contains(vj)) {
							listViNode.add(vj);
						}
						vi.neighbors.add(new Edge(row[i]));
					}
				}

				// add vertex to fragment if not in V
				if (!listViNode.contains(vi)) {
					listViNode.add(vi);
					if (node[1].equals("1"))
						listInNode.add(vi);
					// if vi is out node then unsure has added in listVi

				} else {
					// set label if exists in V
					int index = listViNode.indexOf(vi);

					if (row.length >= 2) {
						listViNode.get(index).label = row[1]; // set label
						listViNode.get(index).neighbors = vi.neighbors;
					}

					if (node[1].equals("1"))
						listInNode.add(listViNode.get(index));

					if (node[1].equals("2"))
						listOutNode.add(listViNode.get(index));
				}
			}
			bNode.close();

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void LoadDataTung(Text value, ArrayList<Vertex> listViNode,
			ArrayList<Vertex> listInNode, ArrayList<Vertex> listOutNode) {
		try {

			byte[] bytes = value.getBytes();
			ByteArrayInputStream arr = new ByteArrayInputStream(bytes);
			BufferedReader bNode = new BufferedReader(
					new InputStreamReader(arr));

			String strLine = null;
			while ((strLine = bNode.readLine()) != null) {
				String[] row = strLine.split(";");
				String[] node = row[0].split(":");
				Vertex vi;
				if (row.length >= 2) {					
					if (!node[1].equals("0")) {
						String[] atrs = row[1].split("\t");
						if (atrs.length >= 2)
							vi = new Vertex(node[0], atrs[2]);
						else
							vi = new Vertex(node[0]);
					}
					else 
						vi = new Vertex(node[0], row[1]);
					
				} else
					vi = new Vertex(node[0]);

				for (int i = 2; i < row.length; i++) {
					String[] cells = row[i].split(":");
					cells[0] = cells[0].replace("_", "");
					vi.neighbors.add(new Edge(cells[0]));
				}

				listViNode.add(vi);
				if (node[1].equals("1"))
					listInNode.add(vi);
				else if (node[1].equals("0"))
					listOutNode.add(vi);

			}
			bNode.close();

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static Vertex getVertex(String name, ArrayList<Vertex> vfi) {
		Vertex tmp = null;
		for (Vertex v : vfi)
			if (v.name.equals(name)) {
				tmp = v;
				break;
			}

		return tmp;
	}

	public static VertexQ getVertexQ(String name, ArrayList<VertexQ> Vq) {
		VertexQ tmp = null;
		for (VertexQ v : Vq)
			if (v.stateName.equals(name)) {
				tmp = v;
				break;
			}
		return tmp;
	}

}
